﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Common.Utils;
using Common.Exp2D;

namespace Common.Particles
{
    
    public abstract class ParticleSystem2D
    {           
        protected ExtendedRandom m_random;

        private Texture2D m_particleTexture;

        private List<Particle2D> m_particleList;
        private List<Particle2D> particlesToRemove = new List<Particle2D>();

        protected Vector2 m_psPosition;
        public Vector2 Position
        {
            get { return m_psPosition; }
            set { m_psPosition = value; }
        }        

        public ParticleSystem2D(Texture2D particleTexture, Vector2 position, bool hasTrail, int trailLength)
        {
            m_random = new ExtendedRandom();
            m_particleList = new List<Particle2D>();
            m_psPosition = position;
            m_particleTexture = particleTexture;
            InitParticlePool(m_particleTexture, hasTrail, trailLength);

        }

        private int m_elapsedUpdateTime = 0;
        protected int m_updateFrequency = 200; //in ms
        protected int m_nbParticlesPerUpdate = 3;

        public int NbParticlesPerUpdate
        {
            get { return m_nbParticlesPerUpdate; }
            set { m_nbParticlesPerUpdate = value; }
        }

        public void Update(GameTime gameTime)
        {
            //n particles each n seconds
            if (m_elapsedUpdateTime < m_updateFrequency)
            {
                m_elapsedUpdateTime += gameTime.ElapsedGameTime.Milliseconds;
            }            

            if (m_elapsedUpdateTime > m_updateFrequency)
            {
                m_elapsedUpdateTime = 0;

                for (int i = 0; i < m_nbParticlesPerUpdate; i++)
                {                    
                    Particle2D particle = GetParticle();
                    if (particle != null)
                    {
                        SetCurrentParticleParameters(ref particle);
                        m_particleList.Add(particle);
                    }
                }
            }

            //Update particles                
            for (int i = 0; i < m_particleList.Count; i++)
            {
                m_particleList[i].Update(gameTime);
                //Remove dead particles
                if (m_particleList[i].Life <= 0)
                {
                    particlesToRemove.Add(m_particleList[i]);
                }
            }

            for (int i = 0; i < particlesToRemove.Count; i++)
                m_particleList.Remove(particlesToRemove[i]);
            //Add dead particles to queue
            ResetDeadParticles(particlesToRemove);
            particlesToRemove.Clear();
        }

        //default particles parameters
        protected virtual void SetCurrentParticleParameters(ref Particle2D particle)
        {
            
        }

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {   
            for (int i = 0; i < m_particleList.Count; i++)
                m_particleList[i].Draw(gameTime, spriteBatch);
        }

        public const int MAX_NB_PARTICLES = 100;        
        private Queue<Particle2D> m_particleQueue;

        private void InitParticlePool(Texture2D texture, bool hasTrail, int trailLength)
        {
            m_particleQueue = new Queue<Particle2D>(MAX_NB_PARTICLES);
            for (int i = 0; i < MAX_NB_PARTICLES; i++)
                m_particleQueue.Enqueue(new Particle2D(texture, Vector2.Zero, hasTrail, trailLength , 0));
        }

        private Particle2D GetParticle()
        {
            Particle2D particle = null;
            if (m_particleQueue.Count > 0)
            {
                particle = m_particleQueue.Dequeue();
            }
            return particle;
        }

        private void ResetDeadParticles(List<Particle2D> particlesList)
        {
            for (int i = 0; i < particlesList.Count ; i++)
            {
                Particle2D particle = particlesList[i];
                particle.Life = 0;
                particle.Position = Vector2.Zero;
                particle.Velocity = Vector2.Zero;
                particle.Color = Particle2D.INITIAL_COLOR;
                m_particleQueue.Enqueue(particle);
            }
        }
    }    

    public class FallingObjects2D
    {
        protected List<Particle2D> m_objects2DList;
        public int ObjectsCount
        {
            get { return m_objects2DList.Count; }
        }

        protected List<Particle2D> m_objectsToRemove;

        protected Random m_random;

        protected const int MIN_X = -2;
        protected const int MAX_X = 2;

        protected const int MIN_Y = -2;
        protected const int MAX_Y = 2;

        protected const int MIN_LIFE = 500;
        protected const int MAX_LIFE = 1000;

        protected double m_changeElaspedTime = 150;
        protected double m_currentElapsedTime = 0;

        
        protected const int OBJECT_MASS = 0;

        public FallingObjects2D()
        {
            m_objects2DList = new List<Particle2D>();
            m_objectsToRemove = new List<Particle2D>();
            m_random = new Random();
        }

        public void Init (List<Object2D> obj2DList)
        {
            m_objects2DList.Clear();
            AddObjectsList(obj2DList);
            m_currentElapsedTime = 0;            
        }

        public void AddObjectsList(List<Object2D> obj2DList)
        {   
            for (int i = 0; i < obj2DList.Count ; i++)
            {
                AddObject(obj2DList[i]);    
            }            
        }

        public void AddObject(Object2D obj2D)
        {
            Particle2D part = new Particle2D (obj2D.Texture , obj2D.Position, m_random.Next (MIN_LIFE , MAX_LIFE), false);            
            part.Velocity = new Vector2(m_random.Next(MIN_X, MAX_X + 1), m_random.Next(MIN_Y, MAX_Y + 1));
            part.Mass = OBJECT_MASS;
            m_objects2DList.Add(part);
        }

        public virtual void Update(GameTime time)
        {
            
            m_objectsToRemove.Clear();
            m_currentElapsedTime += time.ElapsedGameTime.Milliseconds;

            if (m_currentElapsedTime > m_changeElaspedTime)
            {
                m_currentElapsedTime = 0;
                for (int i = 0; i < m_objects2DList.Count; i++)
                {
                    m_objects2DList[i].m_velocity.X = m_random.Next(MIN_X, MAX_X + 1);
                    m_objects2DList[i].m_velocity.Y = m_random.Next(MIN_Y, MAX_Y + 1);
                }
            }

            for (int i = 0; i < m_objects2DList.Count; i++)
            {
                m_objects2DList[i].Update(time);
                if (m_objects2DList[i].Life < 0)
                    m_objectsToRemove.Add(m_objects2DList[i]);
            }

            for (int i = 0; i < m_objectsToRemove.Count; i++)
            {
                m_objects2DList.Remove(m_objectsToRemove[i]);
                m_objectsToRemove[i] = null;
            }
            
        }

        public void Draw(GameTime time, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < m_objects2DList.Count; i++)
                m_objects2DList[i].Draw(time, spriteBatch);
        }
    }

    public class AimingObject2D
    {
        protected List<Object2D> m_objectList;  
        protected List<Vector2> m_targetList;  
        protected List<Vector2> m_objectInitialPos;  

        
        protected int m_animationDuration = 1000; // in ms
        protected int m_currentElapsedTime = 0;        
        protected Vector2 m_target;
        protected Random m_random;        

        public AimingObject2D(Vector2 target)            
        {
            m_target = target;
            m_objectList = new List<Object2D>();
            m_targetList = new List<Vector2>();
            m_objectInitialPos = new List<Vector2>();
            m_random = new Random();
        }

        public void AddObject(Object2D obj2, Vector2 target)
        {
            m_objectList.Add(obj2);
            m_targetList.Add(target);
            m_objectInitialPos.Add(obj2.Position);
        }

        public void Init(List<Object2D> obj2DList, List<Vector2> targetList)
        {
            m_objectList.Clear();
            m_targetList.Clear();
            m_objectInitialPos.Clear();
            AddObjectsList(obj2DList, targetList);
            m_currentElapsedTime = 0;
        }

        public void AddObjectsList(List<Object2D> obj2DList, List<Vector2> targetList)
        {
            for (int i = 0; i < obj2DList.Count; i++)
            {
                AddObject(obj2DList[i], targetList[i]);
            }
        }

        public void Update(GameTime time)
        {
            m_currentElapsedTime += time.ElapsedGameTime.Milliseconds;            
            if (m_currentElapsedTime > m_animationDuration)
            {
                m_currentElapsedTime = m_animationDuration;
                m_objectList.Clear();
            }

            for (int i = 0; i < m_objectList.Count; i++)
            {
                Vector2 newPos = Vector2.SmoothStep(m_objectInitialPos[i], m_targetList[i], m_currentElapsedTime / (float)m_animationDuration);                
                m_objectList[i].Position = newPos;
                m_objectList[i].Rotation += 0.05f * m_random.Next(2, 6);
            }

            
        }

        public void Draw(GameTime time, SpriteBatch spriteBatch)
        {
            for (int i = 0; i < m_objectList.Count; i++)
            {
                m_objectList[i].Draw(time, spriteBatch);
            }
        }

    }
 
    
}
